package gui;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;

import entidadesparaguardardadosteste.CasoDeTeste;

public class ExecutadorDeCasosDeTeste 
{
	private LinkedList<CasoDeTeste> casosDeTeste;
	
	/*o hashmap casosDeTesteERetornosAposExecucao comeca vazio. Quando um caso de teste que
	 * possui um retorno for executado, eu adicionarei ele e o retorno da execucao no hash
	 * */
	private HashMap<CasoDeTeste,Object> casosDeTesteERetornosAposExecucao;
	
	/*o hashmap casosDeTesteESeEleConseguiuSerExecutado comeca vazio.
	 * -Se durante a execucao do caso de teste ocorrer algum erro (ex: metodo nao pode ser
	 * invocado), o caso de teste entrara no hashmap associado a um boolean false
	 * -Se ate apos a invocacao do metodo do caso de teste nao ocorreu nenhum erro, entao
	 * o caso de teste foi executado com sucesso e ele entrara no hashmap associado a um
	 * boolean true*/
	private HashMap<CasoDeTeste, Boolean> casosDeTesteESeEleConseguiuSerExecutado;
	
	
	public ExecutadorDeCasosDeTeste(LinkedList<CasoDeTeste> casosDeTeste)
	{
		this.casosDeTeste = casosDeTeste;
		this.casosDeTesteERetornosAposExecucao = new HashMap<CasoDeTeste,Object>();
		this.casosDeTesteESeEleConseguiuSerExecutado = new HashMap<CasoDeTeste, Boolean>();
	}
	
	public HashMap<CasoDeTeste,Object> getCasosDeTesteERetornosAposExecucao()
	{
		return this.casosDeTesteERetornosAposExecucao;
	}
	
	public HashMap<CasoDeTeste, Boolean> getCasosDeTesteESeEleConseguiuSerExecutado()
	{
		return this.casosDeTesteESeEleConseguiuSerExecutado;
	}
	
	public void executarTodosOsCasosDeTeste()
	{
		for(int i = 0; i < this.casosDeTeste.size(); i++)
		{
			CasoDeTeste umCasoDeTeste = this.casosDeTeste.get(i);
			
			
			
			
			this.executarUmCasoDeTeste(umCasoDeTeste);
			
			
		}
	}
	
	@SuppressWarnings("rawtypes")
	private void executarUmCasoDeTeste(CasoDeTeste casoDeTeste)
	{	
		LinkedList<Object> instanciasDosParametros = 
				casoDeTeste.getValoresDosParametrosDoMetodo();
		DeclaracaoDeMetodo declaracaoDeMetodo = casoDeTeste.getMetodo();
		Method metodo = declaracaoDeMetodo.getMetodoAssociado();
		boolean metodoPossuiRetorno = casoDeTeste.getMetodoAssociadoPossuiRetorno();
		
		Object instanciaDaClasseAssociadaAoCasoDeTeste =
				this.pegarInstanciaDaClasseAssociadaAoCasoDeTeste(casoDeTeste); 
		try 
		{
			if(instanciasDosParametros.size() == 0)
			{
				//o metodo nao tem parametros de entrada. Pode invocar ser nada mesmo
				
				if(metodoPossuiRetorno == false)
				{
					//metodo nao tem retorno, vou so invoca-lo
					metodo.invoke(instanciaDaClasseAssociadaAoCasoDeTeste);
					this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, true);
					this.devolverInstanciaDaClasseAssociadaAoCasoDeTeste(casoDeTeste,
													instanciaDaClasseAssociadaAoCasoDeTeste);
				}
				else
				{
					Object retornoDaExecucaoDoCasoDeTeste =
							metodo.invoke(instanciaDaClasseAssociadaAoCasoDeTeste);
					this.casosDeTesteERetornosAposExecucao.put(casoDeTeste, retornoDaExecucaoDoCasoDeTeste);
					
					this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, true);
					this.devolverInstanciaDaClasseAssociadaAoCasoDeTeste(casoDeTeste,
													instanciaDaClasseAssociadaAoCasoDeTeste);
				}
			}
			else
			{
				//o metodo tem parametros de entrada, primeiro vou passar a linkedlist com eles para um arranjo
				Object[] arranjoInstanciasDosParametros =
						new Object[instanciasDosParametros.size()];
				
				for(int i = 0; i < arranjoInstanciasDosParametros.length; i++)
				{
					
					arranjoInstanciasDosParametros[i] = instanciasDosParametros.get(i);
				}
				
				//agora vou checar se metodo tem retorno ou nao
				if(metodoPossuiRetorno == false)
				{
					//metodo nao tem retorno, vou somente invoca-lo
					metodo.invoke(instanciaDaClasseAssociadaAoCasoDeTeste,
															arranjoInstanciasDosParametros);
					this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, true);
					this.devolverInstanciaDaClasseAssociadaAoCasoDeTeste(casoDeTeste,
													instanciaDaClasseAssociadaAoCasoDeTeste);
				}
				else
				{
					//metodo tem retorno
					Object retornoDaExecucaoDoCasoDeTeste =
							metodo.invoke(instanciaDaClasseAssociadaAoCasoDeTeste,
															arranjoInstanciasDosParametros);
					this.casosDeTesteERetornosAposExecucao.put(casoDeTeste,
															retornoDaExecucaoDoCasoDeTeste);
					
					this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, true);
					this.devolverInstanciaDaClasseAssociadaAoCasoDeTeste(casoDeTeste,
													instanciaDaClasseAssociadaAoCasoDeTeste);
				}
			}
		} 
		catch (IllegalAccessException e) 
		{
			System.out.println("illegal acess em" + casoDeTeste.getIdCasoDeTeste());
			System.out.println(e);
			this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, false);
		} 
		catch (IllegalArgumentException e) 
		{
			System.out.println("illegal argument em" + casoDeTeste.getIdCasoDeTeste());
			System.out.println(e);
			System.out.println("classe que declara o metodo = " + metodo.getDeclaringClass().getSimpleName());
			Class declaringClass = metodo.getDeclaringClass().getClass();
			if(declaringClass.isInstance(instanciaDaClasseAssociadaAoCasoDeTeste) == false)
			{
				System.out.println("objeto nao eh instancia da classe declarada");
			}
			this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, false);
		} 
		catch (InvocationTargetException e) 
		{
			e.printStackTrace();
			this.casosDeTesteESeEleConseguiuSerExecutado.put(casoDeTeste, false);
		}
	}
	
	/*para mantermos a mesma instancia do objeto apos varias execucoes de metodos, vamos 
	 * pegar a instancia do objeto que tem o metodo a ser testado la na fachada*/
	private Object pegarInstanciaDaClasseAssociadaAoCasoDeTeste(CasoDeTeste casoDeTeste)
	{
		FachadaManipulacaoClassesDeUmProjeto fachadaManipulaClassesDoProjeto =
										FachadaManipulacaoClassesDeUmProjeto.getInstance();
		String nomeClasseAssociadaAoCasoDeTeste = 
				casoDeTeste.getNomeClasseAssociadaAoCasoDeTeste();
		
		Object instanciaDaClasse =
				fachadaManipulaClassesDoProjeto.pegarInstanciaDaClasseNoHashMap
														(nomeClasseAssociadaAoCasoDeTeste);
		return instanciaDaClasse;
	}
	
	/*apos invocar um metodo usando uma instancia da classe associada ao caso de teste,
	 * vamos devolver a instancia a fachada para futuros testes.
	 * NO ENTANTO, SE O USUARIO ESCOLHEU NAO ARMAZENAR O ESTADO DAS VARIAVEIS NO SEU PROJETO,
	 * ESSA FUNCAO IRAH REINICAR TODAS AS INSTANCIAS DAS CLASSES NO HASHMAP LA
	 * DA FACHADA! Vamos olhar o singleton e ver se eh necessario armazenar
	 * o estado das variaveis ou nao*/
	private void devolverInstanciaDaClasseAssociadaAoCasoDeTeste
								(CasoDeTeste casoDeTeste, Object instanciaDaClasse)
	{
		SingletonSalvarEstadoDasVariaveis conheceSeOProjetoDeveOuNaoSalvarEstados = 
											SingletonSalvarEstadoDasVariaveis.getInstance();
		boolean salvarEstadoDasVariaveis = 
				conheceSeOProjetoDeveOuNaoSalvarEstados.getSalvarEstadoDasVariaveis();
		
		if(salvarEstadoDasVariaveis == true)
		{
			FachadaManipulacaoClassesDeUmProjeto fachadaManipulaClassesDoProjeto =
					FachadaManipulacaoClassesDeUmProjeto.getInstance();
			
			String nomeDaClasse = casoDeTeste.getNomeClasseAssociadaAoCasoDeTeste();
			
			fachadaManipulaClassesDoProjeto.colocarInstanciaDaClasseNoHashMap(nomeDaClasse, instanciaDaClasse);
		}
		else
		{
			FachadaManipulacaoClassesDeUmProjeto fachadaManipulaClassesDoProjeto =
					FachadaManipulacaoClassesDeUmProjeto.getInstance();
			fachadaManipulaClassesDoProjeto.reiniciarTodasAsInstanciasDoHashMap();
		}
	}
	
}
